//
//  RequestType.swift
//  ADKit
//
//  Created by Adoma on 2020/10/25.
//

import Foundation
import Alamofire
import RxSwift
import SwiftyJSON

public typealias HTTPMethod = Alamofire.HTTPMethod
public typealias Parameters = Alamofire.Parameters
public typealias ProgressHandler = Alamofire.Request.ProgressHandler

public enum Encoding {
    case `default`
    case json
}

extension Encoding {
    var type: ParameterEncoding {
        switch self {
        case .json:
            return JSONEncoding.default
        default:
            return URLEncoding.default
        }
    }
}

public typealias Header = [String: String]

extension Header {
    
    var toHTTPHeaders: HTTPHeaders? { HTTPHeaders(self) }
    
    public var toString: String? {
        try? JSONSerialization.data(withJSONObject: self).toString
    }
}

extension Parameters {
    
    public var toString: String? {
        try? JSONSerialization.data(withJSONObject: self).toString
    }
}

public protocol RequestType: Sendable {
    
    var session: Session { get }

    var host: String { get }
    var path: String { get }
    
    var header: Header? { get }
    var headerModifier: ((inout Header?)->Void)? { get }
    
    var parameters: Parameters? { get }
    var parametersModifier: ((inout Parameters?)->Void)? { get }
    
    var method: HTTPMethod { get }
    var encoding: Encoding { get }
    
    var interceptor: RequestInterceptor? { get }
    
}

public extension RequestType {
    
    var session: Session { .default }
    
    var path: String {
        return ""
    }
    
    var header: Header? {
        return nil
    }
    
    var headerModifier: ((inout Header?)->Void)? {
        return nil
    }
    
    var parameters: Parameters? {
        return nil
    }
    
    var parametersModifier: ((inout Parameters?)->Void)? {
        return nil
    }
    
    var method: HTTPMethod {
        return .get
    }
    
    var encoding: Encoding {
        switch method {
        case .post:
            return .json
        default:
            return .default
        }
    }
    
    var interceptor: RequestInterceptor? {
        return nil
    }
}

public struct Response {
    
    public let duration: String
    public let url: String
    public let method: String
    public let header: Header?
    public let parameters: Parameters?
    public let result: Result<Data?, AFError>
    
    public var value: String? {
        do {
            let data = try result.get()
            return data?.toString
        } catch {
            return error.localizedDescription
        }
    }
    
}

public extension RequestType {
    
    /// by ADKit
    var request: Observable<Response> {
        
        return Observable.create { observer -> Disposable in
            
            let url = host + path
            let method = method
            let encoding = encoding.type
            
            var header = header
            headerModifier?(&header)
            
            var parameters = parameters
            parametersModifier?(&parameters)
            
            let dataRequest = session.request(url,
                                              method: method,
                                              parameters: parameters,
                                              encoding: encoding,
                                              headers: header?.toHTTPHeaders,
                                              interceptor: self.interceptor).response { response in
                
                let duration = response.metrics.map { "\($0.taskInterval.duration)" } ?? "None"
                observer.onNext(.init(duration: duration,
                                      url: url,
                                      method: method.rawValue,
                                      header: header,
                                      parameters: parameters,
                                      result: response.result))
                observer.onCompleted()
                
            }
            
            return Disposables.create {
                dataRequest.cancel()
            }
        }
    }
    
}
